import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import jdk.internal.vm.Continuation;
import jdk.internal.vm.ContinuationScope;

// java --add-opens java.base/jdk.internal.vm=ALL-UNNAMED PrimeC3
public class PrimeC3 {
	static final ContinuationScope cs = new ContinuationScope("");
	static final FastBlockingQueue<Continuation> readyStack = new FastBlockingQueue<>(16384);

	static {
		for (int i = 0, n = Runtime.getRuntime().availableProcessors(); i < n; i++) {
			new Thread(() -> {
				try {
					for (; ; ) {
						var c = (ContinuationEx)readyStack.take();
						c.run();
						var r = c.onAfterRun;
						if (r != null) {
							c.onAfterRun = null;
							r.run();
						}
					}
				} catch (Throwable e) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException ex) {
						throw new RuntimeException(ex);
					}
					//noinspection CallToPrintStackTrace
					e.printStackTrace();
				}
			}, "T" + i).start();
		}
	}

	static final class ContinuationEx extends Continuation {
		Runnable onAfterRun;

		public ContinuationEx(ContinuationScope scope, Runnable target) {
			super(scope, target);
		}
	}

	static final class Lock {
		private final ArrayList<Continuation> waits = new ArrayList<>();
		private final AtomicInteger state = new AtomicInteger();

		void lock() {
			for (; ; ) {
				if (state.compareAndSet(0, 1))
					return;
				if (state.compareAndSet(1, 2)) {
					waits.add(Continuation.getCurrentContinuation(cs));
					pause(() -> state.set(1));
				}
			}
		}

		void unlock() {
			for (; ; ) {
				if (state.compareAndSet(1, 2)) {
					var n = waits.size();
					if (n == 0) {
						state.set(0);
						return;
					}
					var c = waits.remove(n - 1);
					state.set(0);
					resume(c);
					return;
				}
			}
		}
	}

	static final class IntChan {
		static final class ThreadValueNode {
			final Continuation thread = Continuation.getCurrentContinuation(cs);
			int value;
		}

		private final Lock lock = new Lock();
		private ThreadValueNode[] waitNodes = new ThreadValueNode[2];
		private int waitCount;
		private boolean hasValue;

		private ThreadValueNode addWaitQueue() {
			var i = waitCount;
			if (i == waitNodes.length)
				waitNodes = Arrays.copyOf(waitNodes, i + i);
			var node = new ThreadValueNode();
			waitNodes[i] = node;
			waitCount = i + 1;
			return node;
		}

		private ThreadValueNode pollWaitNode() {
			var n = waitCount;
			if (n == 0)
				return null;
			waitCount = --n;
			return waitNodes[n];
		}

		void put(int v) {
			lock.lock();
			if (!hasValue) {
				var node = pollWaitNode();
				if (node != null) {
					lock.unlock();
					node.value = v;
					resume(node.thread);
					return;
				}
				hasValue = true;
			}
			addWaitQueue().value = v;
			pause(lock::unlock);
		}

		int take() {
			lock.lock();
			if (hasValue) {
				var node = pollWaitNode();
				if (node != null) {
					lock.unlock();
					resume(node.thread);
					return node.value;
				}
				hasValue = false;
			}
			var node = addWaitQueue();
			pause(lock::unlock);
			return node.value;
		}
	}

	static void pause() {
		Continuation.yield(cs);
	}

	static void pause(Runnable r) {
		((ContinuationEx)Continuation.getCurrentContinuation(cs)).onAfterRun = r;
		Continuation.yield(cs);
	}

	static void resume(Continuation c) {
		try {
			readyStack.put(c);
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		}
	}

	static void go(Runnable r) {
		resume(new ContinuationEx(cs, r));
	}

	static void generate(IntChan ch) {
		//noinspection InfiniteLoopStatement
		for (int i = 2; ; i++)
			ch.put(i);
	}

	static void filter(IntChan in, IntChan out, int prime) {
		//noinspection InfiniteLoopStatement
		for (; ; ) {
			var i = in.take();
			if (i % prime != 0)
				out.put(i);
		}
	}

	public static void main(String[] args) throws InterruptedException {
		var count = args.length > 0 ? Integer.parseInt(args[0]) : 10000;
		go(() -> {
			var ch = new IntChan();
			var ch0 = ch;
			go(() -> generate(ch0));
			for (int i = 0; ; ) {
				var prime = ch.take();
				if (++i == count) {
					System.out.println(prime);
					System.exit(0);
				}
				var ch1 = ch;
				var ch2 = new IntChan();
				go(() -> filter(ch1, ch2, prime));
				ch = ch2;
			}
		});
		Thread.sleep(1_000_000L);
	}
}
